# -*- coding: utf-8 -*-
import logging
import pickle
import json
from os import rename
import zipfile
import jieba
import os
from app.utils.exception import PredictParamsFormatError, TaskInitError, PredictParamsNotFoundDiscreteError, PredcitError
from app.utils.dateUtil import str_to_datetime, is_date, is_datetime, date_to_days
import numpy as np
import pandas as pd
from xgboost.sklearn import XGBClassifier
from statsmodels.tsa.arima_model import ARIMA, ARIMAResultsWrapper
import algo.ModelTrainMange as modelTrains

logger = logging.getLogger(__name__)


def unzip_file(zip_src, dst_dir):  # 修复解压乱码
    with zipfile.ZipFile(zip_src, 'r') as fd:
        for i in fd.namelist():
            try:
                gbkfilename = i.encode('cp437').decode('GBK')
                fd.extract(i, dst_dir)
                rename(''.join([dst_dir + '/', i]), ''.join([dst_dir + '/', gbkfilename]))
            except Exception as e:
                fd.extract(i, dst_dir)

class TaskWorker(object):
    def __init__(self, task_id, task_type, model_name, model_path, variable_name=None, labels=None, feature=None, \
                 date_name=None):
        """
    `   预测服务初始化
        :param task_id: 任务Id
        :param task_type: 任务类型 分类/回归
        :param model_name: 模型名称(改为路径+名称)
        :param model_path: 模型路径
        :param variable_name: 变量名称, [attr1, attr2, attr3, attr4] 形式
        :param labels: label对应关系 {'0': 'label1', '1': 'label2', '2': 'label3'}
        :param feature: 离散值对应关系 {'feature1': 0, 'feature2': 1}
        :param date_name: 日期变量 [attr1, attr2, attr3. attr4] 形式
        """
        self.task_id = task_id
        self.task_type = task_type
        self.model_name = model_name
        self.model_path = model_path
        self.variable_name = variable_name
        self.date_name = date_name
        self.labels = labels
        self.feature = feature
        self.is_init = False
        self.model = None

    def get_task_id(self):
        return self.task_id

    def get_task_type(self):
        return self.task_type

    def get_model_name(self):
        return self.model_name

    def get_model_path(self):
        return self.model_path

    def get_is_init(self):
        return self.is_init

    def get_variable_name(self):
        return self.variable_name

    def get_labels(self):
        return self.labels

    def get_feature(self):
        return self.feature

    def get_date_name(self):
        return self.date_name

    def init_model(self):
        """
        初始化模型
        :return:
        """
        try:
            if self.task_type == 'matlab':
                self.model = None
                self.is_init = True
            else:
                logger.info('------预测文件路径------'+ str(self.model_path) + str(self.task_type), exc_info=True)
                #解压压缩包，分别提取txt和save文件
                if (self.task_type == '1001' or self.task_type == '1002' or self.task_type == '1005' or self.task_type == 'sklearn' or self.task_type == '1007' or self.task_type == '1008' or self.task_type == '1009' or self.task_type == '1006') and self.model_path[-4:] =='.zip':
                    file_zip = self.model_path
                    file = file_zip[:-4]
                    unzip_file(file_zip, file)
                    s = os.listdir(file)
                    for string in s:
                        if os.path.splitext(string)[1] == '.sav' or os.path.splitext(string)[1] == '.ftz':
                            self.model_path = (file + '/' + string)
                        elif os.path.splitext(string)[1] == '.txt' and self.task_type == '1001':
                            label_txt = (file + '/' + string)
                            f = open(label_txt, "r", encoding="utf-8")
                            self.labels = dict(json.load(f))['label']
                # 初始化模型，由于初始化后只用于读取模型并预测，所以模型参数和model_name不需要设定，直接读取即可
                logger.info('------预测文件路径------' + str(self.model_path), exc_info=True)
                self.model = modelTrains.ModelTrainMange(model_type=self.task_type,
                                                         model_name=self.model_name, model_params={})
                self.model.load_model(self.model_path)   #后面要改，这里是输入模型./model/xxx.sav文件路径
                self.is_init = True
        except Exception as e:
            logging.error(e, exc_info=True)
            msg = str(self.task_id) + " 模型加载失败";
            logging.error(msg)
            raise TaskInitError(msg)

    def get_discrete_value(self, attr, value):
        """
        判断函数是否为离散值，如果不为离散的直接返回value，如果是的返回对应的离散值，如果不存在离散值，抛出异常，放弃预测
        :param attr:
        :param value:
        :return:
        """
        if self.feature is None:
            if isinstance(value, str):
                raise PredictParamsFormatError(attr + "的值不应该为字符串")
            return value

        if attr not in self.feature:
            if isinstance(value, str):
                raise PredictParamsFormatError(attr + "的值不应该为字符串")
            return value

        if value in self.feature[attr]:
            return self.feature[attr][value]
        else:
            raise PredictParamsNotFoundDiscreteError("当" + attr + "为" + str(value) + "时候的离散值没有找到")

    def get_date_value(self, attr, value):
        """
        判断参数是否为日期的字符串，如果是则会转化为数字，不是直接返回结果
        :param attr
        :param value
        """
        if self.date_name is None:
            return value

        if attr in self.date_name:
            if isinstance(value, str):
                if is_date(value) or is_datetime(value):
                    return date_to_days(str_to_datetime(value))
                else:
                    raise PredictParamsFormatError(attr + "的值应该为日期字符串")
            else:
                raise PredictParamsFormatError(attr + "的值应该为日期字符串")
        else:
            return value

    def predict(self, predict_params):
        """
        预测接口
        predict_params 格式应该为
        [
            {
                'attr1':
                'attr2':
                'attr3':
                'attr4':
            },
            ...
        ]
        :param predict_params: 用于预测的json
        :return:
        """
        predict_value = []
        predict_result = None
        if self.task_type in ['1001', '1002', '1003', '1004', '1005']:
            for items in predict_params:
                value_p = []
                for variable in self.variable_name:
                    if variable in items:
                        value = self.get_date_value(variable, items[variable])
                        value = self.get_discrete_value(variable, value)
                        value_p.append(value)
                if len(value_p) != len(self.variable_name):
                    raise PredictParamsFormatError(str(items))
                predict_value.append(value_p)
            predict_value = pd.DataFrame(predict_value,columns=self.variable_name)
            if self.task_type in ['1001', '1002']:
                predict_value.columns = [i for i in range(len(self.variable_name))]
        if self.task_type in ['sklearn']:
            predict_value = pd.DataFrame(predict_params)
            predict_value.columns = [str(i) for i in range(len(predict_value.columns))]

        logging.info(predict_params)

        if self.task_type in ['1006']:
            stopwords = pd.read_csv("./data/cn_stopwords.txt",
                                    index_col=False, quoting=3, sep="\t", names=['stopword'],
                                    encoding='utf-8')
            stopwords = stopwords['stopword'].values
            def preprocess_text(sentence, stopwords):  # 仅切词过滤，放在loaddata.py
                segs = jieba.lcut(sentence)
                # 去标点、停用词等
                segs = list(filter(lambda x: len(x) > 1, segs))
                segs = list(filter(lambda x: x not in stopwords, segs))
                # 将句子处理成  词语 词语 词语 ……的形式
                return " ".join(segs)
            for items in predict_params:
                variable = self.variable_name[0]
                # variable = 'comment'
                if variable in items:
                    value = self.get_date_value(variable, items[variable])
                    #切词处理
                    value = preprocess_text(value, stopwords)
                    predict_value.append(value)
                else:
                    raise PredictParamsFormatError(str(items))
            df_value = pd.DataFrame({'comment':predict_value})
            predict_value = df_value['comment']

        #图像json数据读取转Series
        if self.task_type in ['1007']:
            img_list = []
            for items in predict_params:
                variable = 'img_array'
                if variable in items:
                    img_list.append(np.asarray(items[variable]))
            predict_value = pd.Series(img_list)
        #音频特征数据读取转df
        if self.task_type in ['1008']:
            predict_value = pd.DataFrame(predict_params)
        if self.task_type == '1005':    #1005时序的输入数据不一样，是一个int值
            # arima_result = self.model.predict(predict_params[0]["period"])
            # predict_result = arima_result[0]
            # predict后返回array,但原代码无tolist()，其余部分有，暂定统一tolist()
            predict_result = []
            for date in predict_params:
                variable = self.variable_name[0]
                predict_result.append(self.model.predict(date[variable]).tolist())
        elif self.task_type in '1009':
            predict_value = pd.DataFrame(predict_params)
            data,predict_result_with_labels = self.model.predict(predict_value)
            ##预测提取需要的列
            predict_result_with_labels = predict_result_with_labels
            return predict_result_with_labels
        elif self.task_type == '1010':
            predict_value = pd.DataFrame(predict_params)
            predict_result_with_labels = self.model.predict((predict_value,'predict'))
            if len(predict_result_with_labels['label']) > 0:
                predict_result_with_labels['label'] = list(map(lambda x: self.labels[x] if x != None else None,predict_result_with_labels['label']))
            return predict_result_with_labels
        else:    # 自定义的modeltrainmange都跑来这里
            predict_result = self.model.predict(predict_value).tolist()


        logging.info(predict_result)

        if predict_result is None:
            raise PredcitError("任务Id: " + self.task_id + " 出现未错误或模型不支持, 预测失败")

        if self.task_type == '1002':
            predict_result_regress = []
            for i in range(0, len(predict_result)):
                regress = str(predict_result[i])
                predict_result_regress.append(regress)
            return predict_result_regress


        #数据预测
        if self.labels is None:
            predict_result_without_labels = []
            for i in range(0, len(predict_result)):
                label = str(predict_result[i])
                predict_result_without_labels.append(label)
            return predict_result_without_labels
        else:
            predict_result_with_labels = []
            for i in range(0, len(predict_result)):
                label = str(predict_result[i])
                try:
                    label = self.labels[str(predict_result[i])]
                except Exception as e:
                    logger.error(e)

                predict_result_with_labels.append(label)

            return predict_result_with_labels



    def predictOne(self, predict_params):
        """
        预测接口
        :param predict_params: dataframe
        :return:
        """
        predict_value = []
        predict_result = None
        if self.task_type in ['1003', '1004', '1005']:
            predict_params.columns = self.variable_name
            predict_value = predict_params

        if self.task_type in ['1001', '1002']:
            predict_params =  predict_params[self.variable_name]
            predict_params.columns = [i for i in range(len(self.variable_name))]
            predict_value = predict_params

        if self.task_type in ['1006']:
            stopwords = pd.read_csv("./data/cn_stopwords.txt",
                                    index_col=False, quoting=3, sep="\t", names=['stopword'],
                                    encoding='utf-8')
            stopwords = stopwords['stopword'].values
            def preprocess_text(sentence, stopwords):  # 仅切词过滤，放在loaddata.py
                segs = jieba.lcut(sentence)
                # 去标点、停用词等
                segs = list(filter(lambda x: len(x) > 1, segs))
                segs = list(filter(lambda x: x not in stopwords, segs))
                # 将句子处理成  词语 词语 词语 ……的形式
                return " ".join(segs)

            variable = self.variable_name[0]
            predict_params.columns = self.variable_name
            predict_params['comment'] = [preprocess_text(predict_params[variable][0], stopwords)]
            predict_value = predict_params['comment']
        #图像json数据读取转Series
        if self.task_type in ['1007']:
            variable = 'img_array'
            predict_value = pd.Series(np.asarray(predict_params[variable]))
        #音频特征数据读取转df
        if self.task_type in ['1008']:
            predict_value = predict_params

        if self.task_type == '1005':    #1005时序的输入数据不一样，是一个int值
            # arima_result = self.model.predict(predict_params[0]["period"])
            # predict_result = arima_result[0]
            # predict后返回array,但原代码无tolist()，其余部分有，暂定统一tolist()

            predict_result = []
            if isinstance(self.variable_name[0], list):
                predict_params.columns = self.variable_name[0]
            else:
                predict_params.columns = [self.variable_name[0]]
            predict_result.append(self.model.predict(predict_params[self.variable_name[0]]).tolist())

        #
        # elif self.task_type in '1009':
        #     predict_value = pd.DataFrame(predict_params)
        #     data,predict_result_with_labels = self.model.predict(predict_value)
        #     ##预测提取需要的列
        #     predict_result_with_labels = predict_result_with_labels
        #     return predict_result_with_labels
        elif self.task_type == '1010':
            predict_result_with_labels = self.model.predict((predict_params,'predict'))
            if len(predict_result_with_labels['label']) > 0:
                predict_result_with_labels['label'] = list(map(lambda x: self.labels[x] if x != None else None,predict_result_with_labels['label']))
            return predict_result_with_labels
        else:    # 自定义的modeltrainmange都跑来这里
            predict_result = self.model.predict(predict_value).tolist()


        if predict_result is None:
            raise PredcitError("任务Id: " + self.task_id + " 出现未错误或模型不支持, 预测失败")

        if self.task_type == '1002':
            predict_result_regress = []
            for i in range(0, len(predict_result)):
                regress = str(predict_result[i])
                predict_result_regress.append(regress)
            return predict_result_regress


        #数据预测
        if self.labels is None:
            predict_result_without_labels = []
            for i in range(0, len(predict_result)):
                label = str(predict_result[i])
                predict_result_without_labels.append(label)
            return predict_result_without_labels
        else:
            predict_result_with_labels = []
            for i in range(0, len(predict_result)):
                label = str(predict_result[i])
                try:
                    label = self.labels[str(predict_result[i])]
                except Exception as e:
                    logger.error(e)

                predict_result_with_labels.append(label)

            return predict_result_with_labels

    def recreate(self, model_name=None, model_path=None, variable_name=None, labels=None, feature=None, date_name=None):
        if model_name is not None and model_path is not None:
            self.model_name = model_name
            self.model_path = model_path

        if variable_name is not None:
            self.variable_name = variable_name

        if labels is not None:
            self.labels = labels

        if feature is not None:
            self.feature = feature

        if date_name is not None:
            self.date_name = date_name

        self.is_init = False
        self.model = None
        self.init_model()
        self.is_init = True
